
using Boilen.Primitives.Members;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Extensions;


namespace Boilen.Primitives {

    public class TestUtil {

        [Fact]
        public void Capitalize_fails_for_null_string( ) {
            string nullValue = null;

            Assert.Throws<ArgumentNullException>( ( ) => Util.Capitalize( nullValue ) );
        }

        [Fact]
        public void Capitalize_fails_for_empty_string( ) {
            string emptyValue = "";

            Assert.Throws<ArgumentException>( ( ) => Util.Capitalize( emptyValue ) );
        }

        [Theory]
        [InlineData( "a", "A" )]
        [InlineData( "string", "String" )]
        public void Capitalize_succeeds_for_valid_arguments( string value, string expected ) {
            string capitalized = Util.Capitalize( value );

            Assert.Equal( capitalized, expected );
        }


        [Fact]
        public void Join_with_string_collection_fails_for_null_collection( ) {
            IEnumerable<string> collection = null;

            Assert.Throws<ArgumentNullException>( ( ) => Util.Join( collection, "" ) );
        }

        [Fact]
        public void Join_with_toString_method_fails_for_null_collection( ) {
            IEnumerable<object> collection = null;
            Func<int, object, string> toString = TestUtil.ToString;

            Assert.Throws<ArgumentNullException>( ( ) => Util.Join( collection, "", toString ) );
        }

        [Fact]
        public void Join_with_toString_method_fails_for_null_toString_method( ) {
            IEnumerable<object> collection = new object[0];
            Func<int, object, string> toString = null;

            Assert.Throws<ArgumentNullException>( ( ) => Util.Join( collection, "", toString ) );
        }

        [Fact]
        public void Join_with_separator_method_fails_for_null_collection( ) {
            IEnumerable<object> collection = null;
            Func<int, bool, string> separator = TestUtil.Separator;
            Func<int, object, string> toString = TestUtil.ToString;

            Assert.Throws<ArgumentNullException>( ( ) => Util.Join( collection, separator, toString ) );
        }

        [Fact]
        public void Join_with_separator_method_fails_for_null_toString_method( ) {
            IEnumerable<object> collection = new object[0];
            Func<int, bool, string> separator = TestUtil.Separator;
            Func<int, object, string> toString = null;

            Assert.Throws<ArgumentNullException>( ( ) => Util.Join( collection, separator, toString ) );
        }

        [Fact]
        public void Join_with_separator_method_fails_for_null_separator_method( ) {
            IEnumerable<object> collection = new object[0];
            Func<int, bool, string> separator = null;
            Func<int, object, string> toString = TestUtil.ToString;

            Assert.Throws<ArgumentNullException>( ( ) => Util.Join( collection, separator, toString ) );
        }


        [Theory]
        [InlineData( (object)new[] { "a" } )]
        [InlineData( (object)new[] { "a", "b" } )]
        [InlineData( (object)new[] { "alpha", "beta", "gamma" } )]
        public void Join_with_string_collection_succeeds_for_valid_arguments( string[] collection ) {
            const string separator = TestUtil.SeparatorString;

            string expectedJoin = string.Join( separator, collection );

            string actualJoin = Util.Join( collection, separator );

            Assert.Equal( actualJoin, expectedJoin );
        }

        [Theory]
        [InlineData( (object)new[] { "a" } )]
        [InlineData( (object)new[] { "a", "b" } )]
        [InlineData( (object)new[] { "alpha", "beta", "gamma" } )]
        public void Join_with_toString_method_( string[] source ) {
            const string separator = TestUtil.SeparatorString;

            string expectedJoin = string.Join( separator, source );
            var collection = source.Select( s => new { Value = s } );

            string actualJoin = Util.Join( collection, separator, ( i, o ) => o.Value );

            Assert.Equal( actualJoin, expectedJoin );
        }

        [Theory]
        [InlineData( (object)new[] { "a" } )]
        [InlineData( (object)new[] { "a", "b" } )]
        [InlineData( (object)new[] { "alpha", "beta", "gamma" } )]
        public void Join_with_separator_method_succeeds_for_valid_arguments( string[] source ) {
            const string separator = TestUtil.SeparatorString;

            string expectedJoin = string.Join( separator, source );
            var collection = source.Select( s => new { Value = s } );

            string actualJoin = Util.Join( collection, TestUtil.Separator, ( i, o ) => o.Value );

            Assert.Equal( actualJoin, expectedJoin );
        }

        [Theory]
        [InlineData( (object)new[] { "a" } )]
        [InlineData( (object)new[] { "a", "b" } )]
        [InlineData( (object)new[] { "alpha", "beta", "gamma" } )]
        public void Join_with_separator_method_succeeds_for_different_last_separator( string[] source ) {
            const string separator = TestUtil.SeparatorString;
            const string lastSeparator = "!";

            string expectedJoin = string.Join( separator, source );
            int lastRegularSeparatorIndex = expectedJoin.LastIndexOf( separator );
            if( lastRegularSeparatorIndex >= 0 )
                expectedJoin = expectedJoin.Substring( 0, lastRegularSeparatorIndex ) + lastSeparator + expectedJoin.Substring( lastRegularSeparatorIndex + 1 );
            var collection = source.Select( s => new { Value = s } );

            string actualJoin = Util.Join( collection, ( i, last ) => last ? lastSeparator : separator, ( i, o ) => o.Value );

            Assert.Equal( actualJoin, expectedJoin );
        }


        [Fact]
        public void Iterate_fails_for_null_collection( ) {
            IEnumerable<int> collection = null;
            Action<int, bool> separate = ( i, last ) => { };
            Action<int, int> operate = ( i, o ) => { };

            Assert.Throws<ArgumentNullException>( ( ) => Util.Iterate( collection, separate, operate ) );
        }

        [Fact]
        public void Iterate_fails_for_null_separation( ) {
            IEnumerable<int> collection = Enumerable.Range( 0, 5 );
            Action<int, bool> separate = null;
            Action<int, int> operate = ( i, o ) => { };

            Assert.Throws<ArgumentNullException>( ( ) => Util.Iterate( collection, separate, operate ) );
        }

        [Fact]
        public void Iterate_fails_for_null_operation( ) {
            IEnumerable<int> collection = Enumerable.Range( 0, 5 );
            Action<int, bool> separate = ( i, last ) => { };
            Action<int, int> operate = null;

            Assert.Throws<ArgumentNullException>( ( ) => Util.Iterate( collection, separate, operate ) );
        }

        [Fact]
        public void Iterate_succeeds_for_empty_collection( ) {
            IEnumerable<int> collection = new int[0];
            Action<int, bool> separate = ( i, last ) => { throw new InvalidOperationException( "Call to seprate not expected." ); };
            Action<int, int> operate = ( i, o ) => { throw new InvalidOperationException( "Call to operate not expected." ); };

            Util.Iterate( collection, separate, operate );
        }

        [Theory]
        [InlineData( 1 )]
        [InlineData( 2 )]
        [InlineData( 5 )]
        public void Iterate_calls_operate_on_each_object( int count ) {
            int[] collection = Enumerable.Range( 0, count ).ToArray( );
            int nextExpectedIndex = 0;
            Action<int, bool> separate = ( i, last ) => { };
            Action<int, int> operate = ( i, o ) => {
                Assert.Equal( i, nextExpectedIndex );
                Assert.Equal( o, collection[nextExpectedIndex] );
                nextExpectedIndex++;
            };

            Util.Iterate( collection, separate, operate );
        }

        [Theory]
        [InlineData( 1 )]
        [InlineData( 2 )]
        [InlineData( 5 )]
        public void Iterate_calls_separate_between_each_object( int count ) {
            int[] collection = Enumerable.Range( 0, count ).ToArray( );
            int nextExpectedIndex = 1;
            Action<int, bool> separate = ( i, last ) => {
                Assert.Equal( i, nextExpectedIndex );
                Assert.Equal( last, i == count - 1 );
                nextExpectedIndex++;
            };
            Action<int, int> operate = ( i, o ) => { };

            Util.Iterate( collection, separate, operate );
        }


        [Fact]
        public void GetAttribute_fails_for_null_member( ) {
            MemberInfo member = null;

            Assert.Throws<ArgumentNullException>( ( ) => Util.GetAttribute<Attribute>( member ) );
        }

        [Fact]
        public void GetAttribute_returns_null_for_missing_attribute( ) {
            MemberInfo member = typeof( object );

            var attribute = Util.GetAttribute<TemplateTypeAttribute>( member );

            Assert.Null( attribute );
        }

        [Fact]
        public void GetAttribute_succeeds_for_declared_attribute( ) {
            MemberInfo member = typeof( object );

            var attribute = Util.GetAttribute<SerializableAttribute>( member );

            Assert.NotNull( attribute );
        }


        [Fact]
        public void GetUsedExtensions_returns_all_used_extensions( ) {
            Extensions expected = Extensions.Freezable | Extensions.Validation;
            Member member1 = new UsedExtensionsMember( Extensions.Freezable );
            Member member2 = new UsedExtensionsMember( Extensions.Validation );

            Extensions actual = Util.GetUsedExtensions( member1, member2 );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void GetUsedExtensions_given_null_members_returns_all_used_extensions( ) {
            Extensions expected = Extensions.Freezable;
            Member member1 = new UsedExtensionsMember( Extensions.Freezable );
            Member member2 = new UsedExtensionsMember( Extensions.Freezable );

            Extensions actual = Util.GetUsedExtensions( member1, null, member2 );

            Assert.Equal( expected, actual );
        }

        [Fact]
        public void GetUsedExtensions_given_empty_members_returns_None( ) {
            Extensions expected = Extensions.None;

            Extensions actual = Util.GetUsedExtensions( );

            Assert.Equal( expected, actual );
        }


        #region Utility

        private const string SeparatorString = "|";


        private static string ToString( int index, object o ) { return (o ?? "").ToString( ); }

        private static string Separator( int index, bool last ) { return TestUtil.SeparatorString; }


        private sealed class UsedExtensionsMember : Member {
            private readonly Extensions usedExtensions_;
            public UsedExtensionsMember( Extensions usedExtensions ) : base( "name" ) { this.usedExtensions_ = usedExtensions; }
            public override Extensions UsedExtensions { get { return this.usedExtensions_; } }
            protected override void WriteCore( ICodeWriter writer ) { }
        }

        #endregion

    }

}
